package com.ihy.handler;

import com.ihy.domain.User;
import com.ihy.service.ServiceUser;
import com.ihy.utils.RedisTemplateUtil;
import com.ihy.vo.ResultResponse;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 登录失败处理器
 * <p>
 * Component注解：
 * 指示带注解的类是“组件”。当使用基于注解的配置和类路径扫描时，这些类被认为是自动检测的候选类。
 * 其他类级注解也可以被视为标识组件，通常是一种特殊类型的组件：例如@Repository注解或AspectJ的@Aspect注解。
 * <p>
 * Autowired注解:
 * 将构造函数、字段、setter方法或config方法标记为由Spring的依赖注入工具自动连接。
 * Autowired注解是按照类型（byType）装配依赖对象，默认情况下它要求依赖对象必须存在，如果允许null值，可以设置它的required属性为false。
 * 如果我们想使用按照名称（byName）来装配，可以结合@Qualifier注解一起使用。(通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下，会使用对象名作为最后的fallback匹配)
 * <p>
 * Override注解：
 * 指示方法声明旨在重写父类型中的方法声明。如果使用此注解类型对方法进行注解，则需要编译器生成错误消息，除非至少满足以下条件之一：
 * 该方法重写或实现在父类型中声明的方法。
 * 该方法具有一个重写的签名，该签名等同于在对象中声明的任何公共方法的签名。
 */
@Slf4j
@Component
public class LoginFailHandler implements AuthenticationFailureHandler, ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
    @Autowired
    private ResultResponse resultResponse;
    @Autowired
    private RedisTemplateUtil redisTemplateUtil;
    @Autowired
    private ServiceUser serviceUser;
    @Autowired
    private User user;

    //身份验证失败处理程序
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("text/html;charset=utf8");
        if (e instanceof AccountExpiredException) {
            // 账号过期
            resultResponse.setMessage("账号过期");
        } else if (e instanceof BadCredentialsException) {
            // 密码错误
            resultResponse.setMessage("密码错误");
        } else if (e instanceof CredentialsExpiredException) {
            // 密码过期
            resultResponse.setMessage("密码过期");
        } else if (e instanceof DisabledException) {
            // 用户被禁用
            resultResponse.setMessage("用户被禁用");
        } else if (e instanceof LockedException) {
            // 用户被锁定
            resultResponse.setMessage("用户被锁定");
        } else if (e instanceof InternalAuthenticationServiceException) {
            // 内部错误
            resultResponse.setMessage("内部错误");
        } else {
            // 其他错误
            resultResponse.setMessage("用户不存在");
        }
        resultResponse.setCode(401);
        log.info("用户登录失败 内容： [{}]", resultResponse.getMessage());
        httpServletResponse.getWriter().write(JSON.toJSONString(resultResponse));
    }

    //监听
    @Override
    public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
        //获取用户输入的用户名
        String userName = (String) event.getAuthentication().getPrincipal();
        resultResponse.setData(null);
        User query = serviceUser.query(user);
        if (query == null) {
            redisTemplateUtil.del(userName + "Frequency");
            //用户名不存在删除缓存
            redisTemplateUtil.del("redisCacheTime::" + userName);
        } else {
            //获取redis中存储的用户名
            Map<Object, Object> objects = redisTemplateUtil.hmget(userName + "Frequency");
            log.info("参数:{}", userName + "Frequency");
            //判断redis中没有用户名信息的时候执行这个逻辑
            if (objects.size() == 0) {
                //每个用户第一次输错密码写入第一次机会 一共三次机会
                HashMap<String, Object> map = new HashMap<>();
                map.put(userName + "Frequency", 1);
                //设置redis用户名与输错密码次数
                redisTemplateUtil.hmset(userName + "Frequency", map, 5000);
                //获取redis中的用户名与输错密码次数
                objects = redisTemplateUtil.hmget(userName + "Frequency");
                for (Map.Entry<Object, Object> entry : objects.entrySet()) {
                    //返回前端输错密码次数
                    resultResponse.setData(entry.getValue());
                }
            } else {
                for (Map.Entry<Object, Object> entry : objects.entrySet()) {
                    HashMap<String, Object> map = new HashMap<>();
                    map.put(userName + "Frequency", (Integer) entry.getValue() + 1);
                    redisTemplateUtil.hmset(userName + "Frequency", map, 5000);
                    objects = redisTemplateUtil.hmget(userName + "Frequency");
                    for (Map.Entry<Object, Object> entrys : objects.entrySet()) {
                        //返回前端输错密码次数
                        resultResponse.setData(entrys.getValue());
                    }
                }
            }
        }
    }
}
